/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2003-2011, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.swing.event;

import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import org.geotools.geometry.Position2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.swing.MapPane;

/**
 * A {@code MouseEvent} with methods to retrieve position in world coordinates.
 *
 * @author Michael Bedward (adapted from code by Cameron Shorter)
 * @since 2.6
 * @version $Id$
 */
public final class MapMouseEvent extends MouseEvent {
    private final Position2D worldCoords;
    private final boolean isWheelEvent;
    private final int wheelAmount;

    /**
     * Creates a new event instance.
     *
     * @param pane the source map pane
     * @param event the source mouse event
     */
    @SuppressWarnings("deprecation")
    public MapMouseEvent(MapPane pane, MouseEvent event) {
        super(
                (Component) pane,
                event.getID(),
                event.getWhen(),
                event.getModifiers(),
                event.getX(),
                event.getY(),
                event.getClickCount(),
                event.isPopupTrigger(),
                event.getButton());

        worldCoords = calculateWorldPos(pane, event);
        isWheelEvent = false;
        wheelAmount = 0;
    }

    /**
     * Creates a new event instance for a mouse wheel event.
     *
     * @param pane the source map pane
     * @param event the source mouse wheel event
     */
    @SuppressWarnings("deprecation")
    public MapMouseEvent(MapPane pane, MouseWheelEvent event) {
        super(
                (Component) pane,
                event.getID(),
                event.getWhen(),
                event.getModifiers(),
                event.getX(),
                event.getY(),
                event.getClickCount(),
                event.isPopupTrigger());

        worldCoords = calculateWorldPos(pane, event);
        isWheelEvent = true;
        wheelAmount = event.getWheelRotation();
    }

    /** Gets the source map pane for this event. */
    @Override
    public MapPane getSource() {
        return (MapPane) super.getSource();
    }

    /**
     * Queries whether this event was generated by a mouse wheel action.
     *
     * @return {@codetrue} if a mouse event
     */
    public boolean isWheelEvent() {
        return isWheelEvent;
    }

    /**
     * Gets the wheel travel amount for a mouse wheel event.
     *
     * @return wheel travel, or 0 if this is not a mouse wheel event
     */
    public int getWheelAmount() {
        return wheelAmount;
    }

    /**
     * Gets the mouse position in world coordinates.
     *
     * @return world position
     */
    public Position2D getWorldPos() {
        return new Position2D(
                worldCoords.getCoordinateReferenceSystem(), worldCoords.x, worldCoords.y);
    }

    /**
     * Gets an envelope of specified width (in world distance units) which is centred on the mouse
     * position.
     *
     * @param widthWorld envelope width in world units
     * @return the envelope
     * @throws IllegalArgumentException if {@code widthWorld} is less than zero
     */
    public ReferencedEnvelope getEnvelopeByWorld(double widthWorld) {
        if (widthWorld < 0) {
            throw new IllegalArgumentException("invalid value for widthWorld: " + widthWorld);
        }

        double halfw = widthWorld / 2;
        Position2D worldPos = getWorldPos();
        return new ReferencedEnvelope(
                worldPos.x - halfw,
                worldPos.x + halfw,
                worldPos.y - halfw,
                worldPos.y + halfw,
                worldPos.getCoordinateReferenceSystem());
    }

    /**
     * Gets an envelope of specified width (in pixels) which is centred on the mouse position.
     *
     * @param widthPixels envelope width in pixels
     * @return the envelope
     * @throws IllegalArgumentException if {@code widthPixels} is less than zero
     */
    public ReferencedEnvelope getEnvelopeByPixels(double widthPixels) {
        if (widthPixels < 0) {
            throw new IllegalArgumentException("invalid value for widthPixels: " + widthPixels);
        }

        Rectangle2D screenRect =
                new Rectangle2D.Double(
                        getX() - (widthPixels / 2),
                        getY() - (widthPixels / 2),
                        widthPixels,
                        widthPixels);

        MapPane pane = getSource();
        Rectangle2D worldRect =
                pane.getScreenToWorldTransform().createTransformedShape(screenRect).getBounds2D();

        return new ReferencedEnvelope(
                worldRect, pane.getMapContent().getCoordinateReferenceSystem());
    }

    /**
     * Helper for constructors to calculate mouse position in world coordinates.
     *
     * @param pane source map pane
     * @param event source mouse event
     * @return position in world coordinates
     */
    private Position2D calculateWorldPos(MapPane pane, MouseEvent event) {
        AffineTransform tr = pane.getScreenToWorldTransform();
        Position2D pos = new Position2D(event.getX(), event.getY());
        tr.transform(pos, pos);
        pos.setCoordinateReferenceSystem(pane.getMapContent().getCoordinateReferenceSystem());
        return pos;
    }
}
